package com.frank.stock4j.dataloader.abstractloader;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.frank.stock4j.dataloader.constant.CommonConstants;
import com.frank.stock4j.tusharepro.BaseBean;
import com.frank.stock4j.tusharepro.TushareProService;
import com.frank.stock4j.tusharepro.entity.CursorEntity;
import com.frank.stock4j.tusharepro.http.Request;
import com.frank.stock4j.tusharepro.mapper.CursorMapper;
import com.google.common.base.CaseFormat;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
public abstract class AbstractLoader<T extends BaseBean, E extends BaseMapper<T>> implements ILoader<T> {
    ThreadLocal<Map<String,Object>> paramMap = new ThreadLocal<>();
    Map<String, Field> idFiledMap;
    Boolean stoped = false;

    public void stop() {
        stoped = true;
    }

    public void param(String key, Object value) {
        Map<String, Object> request = paramMap.get();
        if (ObjectUtils.isEmpty(request)) {
            request = new ConcurrentHashMap<>();
            paramMap.set(request);
        }
        request.put(key, value);
    }

    public Map<String, Object> getCursor() {
        QueryWrapper<CursorEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("table_name", this.getApiName());
        CursorEntity cursor = cursorMapper.selectOne(wrapper);
        if (ObjectUtils.isEmpty(cursor)) {
            return null;
        }
        String json = cursor.getCondition();
        Map<String, Object> map = JSONUtil.parseObj(json);
        return map;
    }

    public void setCursor(Map<String, Object> map) {
        if (ObjectUtils.isEmpty(map)) {
            return;
        }
        String json = JSONUtil.toJsonStr(map);
        QueryWrapper<CursorEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("table_name", this.getApiName());
        CursorEntity cursor = cursorMapper.selectOne(wrapper);
        if (ObjectUtils.isEmpty(cursor)) {
            cursor = new CursorEntity();
            cursor.setTableName(getApiName());
            cursor.setCondition(json);
            cursorMapper.insert(cursor);
        } else {
            cursor.setCondition(json);
            cursorMapper.updateById(cursor);
        }
    }

    @Autowired
    protected TushareProService tushareProService;
    @Autowired
    protected E mapper;
    @Autowired
    protected CursorMapper cursorMapper;
    public abstract List<T> load();

    public abstract String getApiName();

    @SneakyThrows
    protected List<T> innerLoad() {
        stoped = false;
        String requestBean = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, getApiName());
        String requestClassName = CommonConstants.REQUEST_PACKAGE.replace("{className}", requestBean);
        Class<?> requestClass = Class.forName(requestClassName);
        Object request = requestClass.newInstance();
        Request<T> paramRequest = (Request<T>)request;
        Map<String, Object> requestMap = paramMap.get();
        if (!ObjectUtils.isEmpty(requestMap)) {
            for (String key: requestMap.keySet()) {
                paramRequest.param(key, requestMap.get(key));
            }
        }
        String methodName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, getApiName());
        Method method = TushareProService.class.getMethod(methodName, request.getClass());
        Object result = method.invoke(tushareProService, request);
        if (result == null) {
            throw new Exception("发生错误");
        }
        List<T> resultList = (List<T>)result;
        StopWatch stopwatch = new StopWatch();
        stopwatch.start();
        save(resultList);
        stopwatch.stop();
        log.info("saved:{}, time:{}", resultList.size(), stopwatch.getTotalTimeMillis());
        return resultList;
    }

    @SneakyThrows
    public void save(List<T> data) {
        if (CollectionUtils.isEmpty(data)) {
            return;
        }
        Class<?> cls = data.get(0).getClass();
        Map<String, Field> fieldMap = getIdField(cls);

        for (T t: data) {
            if (stoped) {
                return;
            }
            QueryWrapper<T> wrapper = new QueryWrapper<T>();
            for (String key: fieldMap.keySet()) {
                Field f = fieldMap.get(key);
                TableField tableField = f.getAnnotation(TableField.class);
                String name = f.getName();
                if (!ObjectUtils.isEmpty(tableField)) {
                    name = tableField.value();
                }
                Object value = f.get(t);
                wrapper.eq(name, value);
            }
            T tmp = mapper.selectOne(wrapper);
            if (ObjectUtils.isEmpty(tmp)) {
                mapper.insert(t);
            } else {
                mapper.update(t, wrapper);
            }
        }
    }

    public Map<String, Field> getIdField(Class<?> cls) {
        if (ObjectUtils.isEmpty(idFiledMap)) {
            idFiledMap = new HashMap<>();
            Field[] fields = cls.getDeclaredFields();
            for (Field f: fields) {
                TableId tableId = f.getAnnotation(TableId.class);
                if (ObjectUtils.isEmpty(tableId)) {
                    continue;
                }
                f.setAccessible(true);
                idFiledMap.put(f.getName(), f);
            }
        }
        return idFiledMap;
    }
}
